/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package net.worlds;

import java.awt.Toolkit;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.image.BufferedImage;
import net.worlds.lua.ScriptContext;
import net.worlds.component.ErrorDialog;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import javax.swing.UIManager;
import net.worlds.component.CrashFrame;
import net.worlds.component.CrashFrameSimple;
import net.worlds.input.DefaultInputProcessor;
import net.worlds.input.InputProcessor;
import net.worlds.input.InputState;
import net.worlds.net.Client;
import net.worlds.net.Server;
import net.worlds.object.Camera;
import net.worlds.object.GameObject;
import net.worlds.object.NetworkClient;
import net.worlds.object.Player;
import net.worlds.object.PlayerUI;
import net.worlds.ui.PropsTableCellEditor;
import net.worlds.ui.UI;
import net.worlds.ui.edge.EMain;
import net.worlds.ui.edge.EditorOverlay;
import net.worlds.ui.edge.EditorTop;

/**
 *
 * @author Jacob Morgan
 */
public class Main {
    public static String siteURL;
    public static final String version = "20120930";
    public static boolean noView = false;
    public static int clanid = 0;
    public static boolean scriptsStarted = false;
    private static boolean processTasks = false;
    public static int id;
    private static String name = "Guest";
    private static String key;
    private static String mode;
    public static String chatBarText = "Press . to chat";
    public static String siteChatBarText = "";
    public static boolean chatBarActive = false;
    public static boolean canUseTools = true;
    public static String joinScript = null;
    public static Thread mainThread = Thread.currentThread();
    private static MainView gameView;
    private static Game game;
    private static ScriptContext sc;
    private static LocalPlayer localPlayer;
    private static Player player;
    private static final InputProcessor inputProcessor = new DefaultInputProcessor();
    private static final List<Runnable> tasks = Collections.synchronizedList(new ArrayList<Runnable>(WorldsSettings.MAX_TASKS));
    private static final List<Runnable> stepTasks = Collections.synchronizedList(new ArrayList<Runnable>(WorldsSettings.MAX_TASKS));
    private static final ArrayList<Runnable> tempTasks = new ArrayList<Runnable>(WorldsSettings.MAX_TASKS);
    private static Server server;
    private static Client client;
    private static NetworkClient clientObject;
    public static final boolean debug = false;
    private static long lastStatsUpdateTime;
    public static float fps;
    public static float k;
    private static Thread antiCrash;
    private static String crashMessage;
    protected static boolean error = false;
    private static int argc = 0;
    private static MainApplet applet;
    private static GamePanel panel;
    public static boolean loading = false;
    
    /**
     * @param args the command line arguments
     */
    public static Game getGame()
    {
        return game;
    }
    public static MainView getView()
    {
        return gameView;
    }
    public static ScriptContext getScriptContext()
    {
        return sc;
    }
    public static int getLoginID()
    {
        return id;
    }
    public static String getLoginName()
    {
        return name;
    }
    public static String getLoginKey()
    {
        return key;
    }
    private static void processInput() throws WorldsException
    {
        synchronized(InputState.getKeys())
        {
            for(int code : InputState.getKeys())
            {
                inputProcessor.keyDown(code);
            }
        }
        
        synchronized(InputState.getRKeys())
        {
            for(int code : InputState.getRKeys())
            {
                inputProcessor.keyUp(code);
            }
            InputState.clearRKeys();
        }
        synchronized(InputState.getNewKeys())
        {
            for(int code : InputState.getNewKeys())
            {
                inputProcessor.keyTyped(code);
            }
            InputState.clearNewKeys();
        }
        if(InputState.mouseButton == 0 && InputState.pmouseButton > 0)
        {
            inputProcessor.mouseUp(InputState.pmouseButton, InputState.mouseX, InputState.mouseY);
        }
        if(InputState.mouseButton > 0 && InputState.pmouseButton != InputState.mouseButton)
        {
            inputProcessor.mouseDown(InputState.mouseButton, InputState.mouseX, InputState.mouseY);
        }
        InputState.pmouseButton = InputState.mouseButton;
        if(InputState.isMouseClicked() > 0)
            inputProcessor.mouseClicked(InputState.mouseButton, InputState.mouseX, InputState.mouseY);
        if(InputState.mouseX != InputState.pmouseX || InputState.mouseY != InputState.pmouseY)
            inputProcessor.mouseMoved(InputState.mouseX, InputState.mouseY);
        if((InputState.mouseX != InputState.pmouseX || InputState.mouseY != InputState.pmouseY) && InputState.mouseButton > 0)
            inputProcessor.mouseDragged(InputState.mouseX, InputState.mouseY, InputState.mouseButton);
        int r = InputState.getMouseWheelRotation();
        if(r != 0)
            inputProcessor.mouseWheelMoved(r);
        InputState.pmouseX = InputState.mouseX;
        InputState.pmouseY = InputState.mouseY;
    }
    public static void runInMainThread(final Runnable r)
    {
        if(Thread.currentThread() == mainThread)
            r.run();
        else
        {
            synchronized(tasks)
            {
                tasks.add(r);
            }
        }
    }
    public static void addStepTask(Runnable r)
    {
        synchronized(stepTasks)
        {
            stepTasks.add(r);
        }
    }
    public static void addTempTask(Runnable r)
    {
        tempTasks.add(r);
    }
    public static void processTempTasks()
    {
        Iterator<Runnable> itr = tempTasks.iterator();
        while(itr.hasNext())
        {
            itr.next().run();
            itr.remove();
        }
    }
    public static int getNumTasks()
    {
        return tasks.size();
    }
    private static void processTasks()
    {
        synchronized(tasks)
        {
            Iterator<Runnable> itr = tasks.iterator();
            while(itr.hasNext())
            {
                Runnable task = itr.next();
                try
                {
                    task.run();
                }
                catch(RuntimeException ex)
                {
                    Main.log(ex.toString());
                    //if(debug)
                        ex.printStackTrace();
                }
                itr.remove();
            }
        }
        if(stepTasks.size() > 0 && !(game != null && game.getCurrentWorld() != null && game.isPlaying))
            processStepTasks();
    }
    public static void processStepTasks()
    {
        ArrayList<Runnable> tc;
        synchronized(stepTasks)
        {
            Iterator<Runnable> itr = stepTasks.iterator();
            while(itr.hasNext())
            {
                Runnable task = itr.next();
                try
                {
                    task.run();
                }
                catch(RuntimeException ex)
                {
                    if(ex.getMessage() != null)
                        Main.log(ex.getMessage());
                    else
                    {
                        Main.log(ex.toString());
                        ex.printStackTrace();
                    }
                }
                itr.remove();
            }
        }
    }
    public static void error(Throwable ex)
    {
        error(ex, false);
    }
    public static void error(Throwable ex, boolean redownload)
    {
        if(antiCrash == null || Thread.currentThread() != Main.mainThread)
        {
            new ErrorDialog(ex.toString(), redownload).setVisible(true);
        }
        ex.printStackTrace();
        crashMessage = ex.toString()+"\n";
        for(StackTraceElement e : ex.getStackTrace())
        {
            crashMessage += e.toString()+"\n";
        }
    }
    public static void log(final String s, Exception ex)
    {
        DateFormat dfm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String date =  dfm.format(new Date());
        String m = "["+date+"] "+s;
        if(Main.isBuildServer())
            Main.getServer().sendConsoleOutput(s);
        System.err.println(m);
        if(ex != null)
            ex.printStackTrace();
    }
    public static void log(final String s)
    {
        if(s != null)
            log(s.replace("java.lang.RuntimeException: ", ""), null);
    }
    public static void addPlayer(String name)
    {
        String script = Util.getTextFromResource("/createCharacter.lua");
        script = script.replace("$NAME", name);
        sc.createScriptThread(null, "AddPlayer", script).start();
    }
    public static void removePlayer()
    {
        try {
            setLocalPlayer(null);
        } catch (WorldsException ex) {
            ex.printStackTrace();
        }
        String script = Util.getTextFromResource("/removeCharacter.lua");
        script = script.replace("$NAME", name);
        sc.createScriptThread(null, "RemovePlayer", script).start();
    }
    public static void setLocalPlayer(Player p) throws WorldsException
    {
        setLocalPlayer(p, true);
        if(p != null && p.hasChild("UI") && p.getChild("UI") instanceof PlayerUI)
            ((PlayerUI)p.getChild("UI")).setPlayer();
    }
    public static void setLocalPlayer(Player p, boolean isUser) throws WorldsException
    {
        Player oldPlayer = player;
        player = p;
        if(p != null)
        {
            try {
                if(Main.isEditor())
                Main.getEditorOverlay().addRemovePlayerItem.setImage(AssetManager.loadImage("removeplayer.png", "removeplayer.png"));
                localPlayer = new LocalPlayer(p);
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        }
        else
        {
            try {
                if(Main.isEditor())
                    Main.getEditorOverlay().addRemovePlayerItem.setImage(AssetManager.loadImage("addplayer.png", "addplayer.png"));
                localPlayer = null;
                if(!game.getCurrentWorld().destroyed)
                {
                    game.currentCamera = (Camera) game.getCurrentWorld().getService("Camera");
                    game.currentCamera.setProp("FollowObject", null, true);
                }
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        }
        if(isUser)
        {
            if(p != null)
                p.world.localPlayer = p;
            else if(oldPlayer != null)
                oldPlayer.world.localPlayer = null;
        }
        if(p != null && game.getCurrentWorld() != p.world)
            game.setCurrentWorld(p.world.name);
    }
    public static LocalPlayer getLocalPlayer()
    {
        return localPlayer;
    }
    public static boolean isLocalPlayer(Player p)
    {
        return localPlayer.getPlayer() == p;
    }
    public static Player getPlayer()
    {
        return player;
    }
    public static void setCurrentGame(Game g)
    {
        game = g;
        //w.rebuild();
    }
    public static void newGame()
    {
        game = new Game();
    }
    public static String fixOldLevel(String script)
    {
        script = (script.replace("game:scheduleTask(function()", "").trim()+"----X").replace("end)----X", "");
        return script;
    }
    private static String fixBugs(String script)
    {
        if(script.indexOf("game:setType(0)") > -1)
        {
            script = script.replace("game:setType(0)\n--World", "--World");
            script = script.replace("function c(p) return create(p) end", "game:setType(0)\nfunction c(p) return create(p) end");
        }
        else if(script.indexOf("game:setType(1)") > -1)
        {
            script = script.replace("game:setType(1)\n--World", "--World");
            script = script.replace("function c(p) return create(p) end", "game:setType(1)\nfunction c(p) return create(p) end");
        }
        return script.replace("\\[\\[\\", "\\[\\\\[\\").replace("\\]\\]\\", "\\]\\\\]\\");
    }
    public static void loadFromFile(String path) throws IOException
    {
        setLoading(true);
        if(!noView)
            panel.repaint();
        game.getCurrentWorld().setMessage("Loading...");
        final String contents = Util.readFileAsString(path);
        loadGame(contents);
        finishLoading(true);
        game.stop(false);
    }
    public static void loadModelFromFile(String path) throws IOException, WorldsException
    {
        String contents = Util.readFileAsString(path);
        loadModel(contents);
    }
    public static void loadModel(String contents) throws WorldsException
    {
        contents = contents.replace("\\[\\[\\", "\\[\\\\[\\").replace("\\]\\]\\", "\\]\\\\]\\");
        GameObject parent;
        if(UI.getNumberSelected() == 1)
            parent = UI.getSelected().get(0);
        else
            parent = game.getCurrentWorld().getService("World");
        String end = "if main:isA(\"Box\") or main:isA(\"Circle\") then main.Position = game.Camera.Position; elseif main:isA(\"Tool\") or main:isA(\"DragTool\") or main:isA(\"CopyTool\") or main:isA(\"GrabTool\") or main:isA(\"RotateTool\") or main:isA(\"DeleteTool\") then main.Parent = game.StarterInventory; end; print(\"Model added to \"..main.Parent:getPath())";
        contents = (contents.replace("game:scheduleTask(function()", "").trim()+"----X").replace("end)----X", "");
        contents = contents.replace("$PARENT", ""+parent.getID())+"\n"+end;
        String[] parts = contents.split("\n");
        String type = parts[0].replace("--", "");
        final int modelID = Integer.parseInt(parts[1].replace("--", "").replace("\r", ""));
        final String script = contents.replace("p"+modelID, "main").replace("game:get("+modelID+")", "main");
        Runnable r = new Runnable()
        {
            public void run() {
                Main.getScriptContext().executeScript(script);
            }
        };
        Main.runInMainThread(r);
    }
    public static void loadFromResource(String path, boolean clear) throws IOException
    {
        final String contents = fixOldLevel(Util.getTextFromResource(path));
        setLoading(true);
        if(!noView)
            panel.repaint();
        game.getCurrentWorld().setMessage("Loading...");
        loadGame(contents);
        finishLoading(true);
        game.play(false);
    }
    public static void loadFromID(int id) throws IOException
    {
        if(id < 0)
        {
            clanid = -id;
            new Thread(new Runnable()
            {
                public void run() {
                    while(true)
                    {
                        try {
                        Util.httpGet("http://"+Main.siteURL+"/game/clanbuildstatus.php?user="+Main.getLoginID()+"&key="+Main.getLoginKey());
                            Thread.sleep(10 * 1000);
                        } catch (Exception ex) {
                            log("Failed to update clan build status: "+ex.toString());
                        }
                    }
                }
            }).start();
        }
        final String contents = Util.httpGet("http://"+Main.siteURL+"/game/get.php?user="+Main.getLoginID()+"&key="+Main.getLoginKey()+"&id="+id);
        if(!contents.contains("$/|/_"))
        {
            game.getCurrentWorld().setMessage("Failed to load game");
            if(noView)
                WorldsCLI.exit = true;
            throw new RuntimeException(contents);
        }
        String[] parts = contents.split("\\$\\/\\|\\/_");
        final boolean canUseTools2 = Boolean.parseBoolean(parts[0]);
        final String data = parts[1];
        setLoading(true);
        if(!noView)
            panel.repaint();
        game.getCurrentWorld().setMessage("Loading...");
        loadGame(data);
        Runnable r = new Runnable()
        {
            public void run() {
                if (canUseTools2 && (mode == null || !mode.equals("play"))) {
                    finishLoading(true);
                    game.stop(false);
                } else {
                    finishLoading(false);
                    UI.clearMouseMode();
                    runInMainThread(new Runnable()
                    {
                        public void run() {
                            game.play(false);
                        }
                    });
                    if (!noView && canUseTools && false) {
                        UI.hideTools();
                    }
                    if(!noView && (mode == null || !mode.equals("server")))
                        addPlayer(name);
                }
            }
        };
        Main.runInMainThread(r);
    }
    public static void runScripts()
    {
        game.runScripts();
    }
    public static void parseArgs(String[] args)
    {
        argc = args.length;
        if(args.length == 3)
        {
            id = Integer.parseInt(args[0]);
            name = args[1];
            key = args[2];
        }
        else if(args.length == 6)
        {
            id = Integer.parseInt(args[0]);
            name = args[1];
            key = args[2];
            mode = args[3];
            canUseTools = Boolean.parseBoolean(args[4]);
            joinScript = args[5];
        }
        else
            id = 0;
    }
    public static void debug(String message)
    {
        if(debug)
            System.out.println(message);
    }
    private static void setup1() throws WorldsException
    {
        debug("Loading settings");
        Settings.restore();
        fps = Settings.getInt("FPS");
        k = 1000f/fps;
        debug("Loading objects");
        ObjectList.init();
        try {
            AssetManager.loadImage("Unknown", "resource://objects/Unknown.png");
        } catch (IOException ex) {
            throw new WorldsException(ex.toString());
        }
    }
    public static void setView(MainView v)
    {
        gameView = v;
        applet.removeComponents();
        panel.setView(v);
        v.setSize(panel.getWidth(), panel.getHeight());
        v.onCreated();
    }
    private static void createView(MainApplet frame)
    {
        applet = frame;
        panel = new GamePanel();
        applet.setMainPanel(panel);
        setView(new LoadingView());
    }
    public static void setLoading(boolean v)
    {
        loading = true;
        if(noView)
            return;
        setView(new LoadingView());
    }
    private static void setup2()
    {
        debug("Creating game");
        debug("Initializing Lua");
        debug("Creating new game");
        newGame();
        sc = new ScriptContext();
        //game.setupWorlds();
        if(!noView)
            applet.setVisible(true);
        if(joinScript == null)
        {
            setView(new EditorView());
            loadGame(Util.getTextFromResource("/default.worlds"));
            game.play(false);
            finishLoading();
        }
        else
        {
            game.getCurrentWorld().setMessage("Requesting game info...");
            String script = Util.httpGet(joinScript);
            sc.executeScript(script);

        }
        if(!noView)
        {
            if(getEditorTop() != null)
                getEditorTop().updateWorldMenu();
        }
        antiCrash = new Thread(new Runnable()
        {
            public void run() {
                while(!WorldsCLI.exit)
                {
                    try {
                        Thread.sleep(2000);
                        if(error)
                        {
                            if(noView)
                            {
                                WorldsCLI.exit = true;
                            }
                            else
                            {
                                if(canUseTools)
                                    new CrashFrame(crashMessage).setVisible(true);
                                else
                                    new CrashFrameSimple(crashMessage).setVisible(true);
                            }
                            break;
                        }
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                }
            }
        });
        antiCrash.setName("AntiCrash");
        antiCrash.start();
    }
    public static void init(MainApplet applet) throws WorldsException
    {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
        try {
            siteURL = applet.getDocumentBase().getHost();
        }
        catch(Exception ex)
        {
            siteURL = "";
        }
        if(siteURL.isEmpty() || !siteURL.contains("2dworlds"))
            siteURL = "2dworlds.org";
        setup1();
        debug("Creating view");
        if(applet == null)
            noView = true;
        else
        {
            UI.viewVisible = true;
            createView(applet);
        }
        setup2();
        if((argc == 0 || argc == 3) && !noView && isEditor())
            getEditorOverlay().showNewFrame();

        System.gc();
    }
    public static void gameStep() {
        try { 
            if(!Main.noView)
                processInput();
            processTasks();
            game.step();
            if (UI.viewVisible) {
                panel.repaint();
                /*if (UI.repaintUI || UI.repaintAdvancedTools || localPlayer != null || UI.menuOpen || !isEditor()) {
                    panel.repaint();
                    UI.repaintUI = false;
                    UI.repaintAdvancedTools = false;
                } else if (localPlayer == null && !UI.repaintUI) {
                    if (UI.advancedToolsVisible) {
                        panel.repaint(0, 55, panel.getWidth() - 219, panel.getHeight() - 55);
                    } else {
                        panel.repaint(0, 55, panel.getWidth(), panel.getHeight() - 55);
                    }
                }*/
            }
            if(!noView)
            {
                Toolkit.getDefaultToolkit().sync();
                if (System.currentTimeMillis() - lastStatsUpdateTime > 250) {
                    lastStatsUpdateTime = System.currentTimeMillis();
                    game.getCurrentWorld().updateStats();
                    if(!PropsTableCellEditor.cellEditing)
                        UI.updatePropsTable();
                }
            }
            try {
                Thread.sleep((int) (k));
            } catch (InterruptedException ex) {
            }
        } catch (WorldsException ex) {
            throw new RuntimeException(ex);
        }
    }
    public static void loadGame(String contents)
    {
        loadGame(contents, true);
    }
    public static void loadGame(final String contents, final boolean disconnectClient)
    {
        Runnable r = new Runnable()
        {
            public void run() {
                try {
                    resetWorld(disconnectClient);
                    String script = fixBugs(contents);
                    game = new Game();
                    game.load(script);
                    sc.executeScript(Util.getTextFromResource("/editor.lua"));
                    game.setMainWorld();
                    if(game.currentCamera != null)
                        game.currentCamera.zoomToCenter();
                    game.stop(false);
                    if(!noView)
                        panel.repaint();
                    game.getCurrentWorld().setMessage(null);
                } catch (WorldsException ex) {
                    ex.printStackTrace();
                }
            }
        };
        Main.runInMainThread(r);
    }
    public static void finishLoading(boolean showTools)
    {
        if(!noView)
        {
            if(showTools)
                setView(new EditorView());
            else
                setView(new GameView());
        }
        else
            loading = false;
        finishLoading();
    }
    public static void showMenu()
    {
        setView(new MenuView());
        loadGame(Util.getTextFromResource("/default.worlds"));
        finishLoading();
    }
    private static void finishLoading()
    {
        game.setMainWorld();
        if(isEditor())
        {
            Main.getEditorTop().updateWorldMenu();
            if(game.type == 0)
                getEditorTop().setSideScrollingButtons(true);
            else
                getEditorTop().setSideScrollingButtons(false);
        }
        if(!noView)
        {
            if(game.getMainWorld().scaleBackground)
                Main.getView().updateBackground();
            else
                Main.getView().updatePaint(game.getMainWorld());
        }
        try {
            if(game.getMainWorld().getService("World").getID() == 31337 && isEditor())
                getEditorOverlay().showNewFrame();
        } catch (WorldsException ex) {
        }
    }
    public static void setServer(Server s)
    {
        server = s;
    }
    public static void setClient(Client c)
    {
        client = c;
    }
    public static void setClientObject(NetworkClient c)
    {
        clientObject = c;
    }
    public static Server getServer()
    {
        return server;
    }
    public static Client getClient()
    {
        return client;
    }
    public static NetworkClient getClientObject()
    {
        return clientObject;
    }
    public static boolean isServer()
    {
        return server != null;
    }
    public static boolean isBuildServer()
    {
        return server != null && server.isBuildServer;
    }
    public static boolean isClient()
    {
        return client != null;
    }
    public static boolean isBuildClient()
    {
        return client != null && client.isBuildClient;
    }
    public static boolean isEditor()
    {
        return !noView && gameView instanceof EditorView;
    }
    public static boolean isMenu()
    {
        return !noView && gameView instanceof MenuView;
    }
    public static EMain getOverlay()
    {
        return gameView.getOverlay();
    }
    public static EditorOverlay getEditorOverlay()
    {
        if(gameView instanceof EditorView)
            return (EditorOverlay) ((MainView)gameView).getOverlay();
        else
            return null;
    }
    public static EditorTop getEditorTop()
    {
        if(gameView instanceof EditorView)
            return (EditorTop) ((MainView)gameView).getTop();
        else
            return null;
    }
    public static MainApplet getApplet()
    {
        return applet;
    }
    public static BufferedImage getImage()
    {
        return panel.getImage();
    }
    public static void setCursor(java.awt.Cursor c)
    {
        panel.setCursor(c);
    }
    public static void mouseOverUI()
    {
        panel.mouseOverUI();
    }
    public static void mouseOffUI()
    {
        panel.mouseOffUI();
    }
    public static void requestFocus()
    {
        panel.requestFocus();
    }
    public static void resetWorld()
    {
        resetWorld(false);
    }
    public static void resetWorld(boolean disconnectClient)
    {
        if(disconnectClient && getClient() != null)
        {
            getClient().disconnect();
            setClient(null);
            setClientObject(null);
        }
        else if(getServer() != null)
        {
            getServer().stop();
            setServer(null);
        }
        sc.close();
        sc = new ScriptContext();
    }
    public static void exit()
    {
        if(sc != null)
            sc.close();
    }
}
